# this class is responsible for managing all
# necessary information during translation
# include chunks (which are collections of slices), slices and
# their results

from typing import Callable, Dict, KeysView, List, Set

from chernc.core.result import ChunkResult, SnippetResult, UseManager
from chernc.slicer.code_chunk import CodeChunk
from chernc.slicer.code_slice import CodeSlice


class ContextManager:
    """
    This class manage all global infomation throughout the translation.

    extern_decls (Set): all the external crates used in the result. This should be added to the entry file.
    extern_uses (UseManager): all the use of external crates' module. All of the result files share the same external use statements.
    """

    def __init__(self):
        self.chunks: List[CodeChunk] = []

        # result, either for a chunk or for a slice:
        self.chunk_results: Dict[CodeChunk, ChunkResult] = {}
        self.slice_results: Dict[CodeSlice, SnippetResult] = {}
        self.finished_count_binder: List[Callable[[int]]] = []
        self.extern_uses: UseManager = UseManager()
        self.extern_decls: Set[str] = set()

    def clear(self):
        self.chunks = []
        self.chunk_results = {}
        self.finished_count_binder = []

    def close(self):
        self.clear()

    def __enter__(self):
        self.clear()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def bind_finished_count(self, bind: Callable[[int], None]) -> None:
        self.finished_count_binder.append(bind)

    def feed_chunks(self, chunks: List[CodeChunk]) -> None:
        self.chunks = chunks.copy()

    def has_chunk_res(self, chunk: CodeChunk) -> bool:
        return chunk in self.chunk_results
    
    def query_chunk_res(self, chunk: CodeChunk) -> ChunkResult:
        if chunk not in self.chunk_results:
            raise Exception(f"[fatal error] there is no result for the chunk {chunk.id}!")
        return self.chunk_results[chunk]

    def has_slice_res(self, slice: CodeSlice) -> bool:
        return slice in self.slice_results
    
    def query_slice_res(self, slice: CodeSlice) -> SnippetResult:
        if slice not in self.slice_results:
            raise Exception(f"[fatal error] there is no result for the slice {slice.loc}!")
        return self.slice_results[slice]

    def query_chunk_decls(self, chunk: CodeChunk) -> str:

        # chunk = self.chunks[chunk_idx]
        decls = "\n".join(["\n".join([f"Decl a {t} with name {name} " for t, name in s.decls]) for s in chunk.slices])
        return decls

    def finished_chunks(self) -> KeysView[CodeChunk]:
        return self.chunk_results.keys()

    def unfinished_chunks(self) -> Set[CodeChunk]:
        return set(self.chunks) - self.chunk_results.keys()

    def finish_one_chunk(self, chunk: CodeChunk, extern_decls: Set[str], uses: UseManager, result: ChunkResult) -> None:
        self.extern_uses = uses | self.extern_uses
        self.extern_decls |= extern_decls
        for extern_decl in self.extern_decls:
            self.extern_uses.dont_use(extern_decl)
        self.chunk_results[chunk] = result
        for s, r in result.result_table.items():
            self.slice_results[s] = r
        finished_count = len(self.finished_chunks())
        for binder in self.finished_count_binder:
            binder(finished_count)

        # self.finished_idx = self.finished_idx + 1

    # def withdraw_one_chunk(self, chunk: CodeChunk) -> None:
    #     """这个有bug，暂时不用。worklist是deque[CodeChunk]，但是worklist.append(d)，d是CodeSlice

    #     :param chunk: _description_
    #     :type chunk: CodeChunk
    #     """
    #     worklist = deque([chunk])
    #     while worklist:
    #         chunk = worklist.popleft()
    #         if chunk not in self.chunk_results:
    #             continue
    #         del self.chunk_results[chunk]
    #         for s in chunk.slices:
    #             for d in s.depends:
    #                 worklist.append(d)

    #     finished_count = len(self.finished_chunks())
    #     for binder in self.finished_count_binder:
    #         binder(finished_count)

    def all_finished(self) -> int:
        return len(self.unfinished_chunks()) == 0

    def __iter__(self):
        return iter(self.unfinished_chunks())
